package com.liuqianfei.markme.rules.block;

import com.liuqianfei.markme.common.Utils;
import com.liuqianfei.markme.helpers.MarkMeHelper;
import com.liuqianfei.markme.helpers.Result;
import com.liuqianfei.markme.rules.AbstractState;
import com.liuqianfei.markme.rules.IAction;
import com.liuqianfei.markme.rules.Reference;
import com.liuqianfei.util.StringUtil;

import java.util.HashMap;
import java.util.List;

/**
 * @author liuqianfei
 */
public class ReferenceAction implements IAction
{
    @Override
    public boolean parse(AbstractState state, boolean silent, int startLine, int _endLine)
    {
        char ch;
        int destEndPos;
        int destEndLineNo;
        int endLine;
        String href;
        int i;
        int l;
        String label;
        int labelEnd = 0;
        String oldParentType;
        Result res;
        int start;
        String str;
        boolean terminate;
        List<IAction> terminatorRules;
        String title;
        int lines = 0;
        int pos = state.getbMarks(startLine) + state.gettShift(startLine);
        int max = state.geteMarks(startLine);
        int nextLine = startLine + 1;

        if (state.getSrc().charAt(pos) != 0x5B/* [ */)
            return false;

        // Simple check to quickly interrupt scan on [link](url) at the start of line.
        // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54
        while (++pos < max)
        {
            if (state.getSrc().charAt(pos) == 0x5D /* ] */
                    && state.getSrc().charAt(pos - 1) != 0x5C/* \ */)
            {
                if (pos + 1 == max)
                    return false;
                if (state.getSrc().charAt(pos + 1) != 0x3A/* : */)
                    return false;
                break;
            }
        }

        endLine = state.getLineMax();

        // jump line-by-line until empty one or EOF
        terminatorRules = state.getMd().parserBlock.ruler.getRules("reference");

        oldParentType = state.getParentType();
        state.setParentType("reference");

        for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++)
        {
            // this would be a code block normally, but after paragraph
            // it's considered a lazy continuation regardless of what's there
            if (state.getsCount(nextLine) - state.getBlkIndent() > 3)
                continue;

            // quirk for blockquotes, this line should already be checked by that rule
            if (state.getsCount(nextLine) < 0)
                continue;

            // Some tags can terminate paragraph without empty line.
            terminate = false;
            for (i = 0, l = terminatorRules.size(); i < l; i++)
            {
                if (terminatorRules.get(i).parse(state, true, nextLine, endLine))
                {
                    terminate = true;
                    break;
                }
            }
            if (terminate) { break; }
        }

        str = state.getLines(startLine, nextLine, state.getBlkIndent(), false).trim();
        max = str.length();

        for (pos = 1; pos < max; pos++)
        {
            ch = str.charAt(pos);
            if (ch == 0x5B /* [ */)
            {
                return false;
            }
            else if (ch == 0x5D /* ] */)
            {
                labelEnd = pos;
                break;
            }
            else if (ch == 0x0A /* \n */)
            {
                lines++;
            }
            else if (ch == 0x5C /* \ */)
            {
                pos++;
                if (pos < max && str.charAt(pos) == 0x0A)
                    lines++;
            }
        }

        if (labelEnd < 0 || str.charAt(labelEnd + 1) != 0x3A/* : */) { return false; }

        // [label]:   destination   'title'
        //         ^^^ skip optional whitespace here
        for (pos = labelEnd + 2; pos < max; pos++)
        {
            ch = str.charAt(pos);
            if (ch == 0x0A)
            {
                lines++;
            }
            else if (Utils.isSpace(ch))
            {
                /*eslint no-empty:0*/
            }
            else
                break;
        }

        // [label]:   destination   'title'
        //            ^^^^^^^^^^^ parse this
        res = MarkMeHelper.parseLinkDestination(str, pos, max);
        if (!res.ok) { return false; }

        href = state.getMd().normalizeLink(res.str);
        if (!state.getMd().validateLink(href)) { return false; }

        pos = res.pos;
        lines += res.lines;

        // save cursor state, we could require to rollback later
        destEndPos = pos;
        destEndLineNo = lines;

        // [label]:   destination   'title'
        //                       ^^^ skipping those spaces
        start = pos;
        for (; pos < max; pos++)
        {
            ch = str.charAt(pos);
            if (ch == 0x0A)
            {
                lines++;
            }
            else if (Utils.isSpace(ch))
            {
                /*eslint no-empty:0*/
            }
            else
            {
                break;
            }
        }

        // [label]:   destination   'title'
        //                          ^^^^^^^ parse this
        res = MarkMeHelper.parseLinkTitle(str, pos, max);
        if (pos < max && start != pos && res.ok)
        {
            title = res.str;
            pos = res.pos;
            lines += res.lines;
        }
        else
        {
            title = "";
            pos = destEndPos;
            lines = destEndLineNo;
        }

        // skip trailing spaces until the rest of the line
        while (pos < max)
        {
            ch = str.charAt(pos);
            if (!Utils.isSpace(ch)) { break; }
            pos++;
        }

        if (pos < max && str.charAt(pos) != 0x0A)
        {
            if (StringUtil.isNotEmpty(title))
            {
                // garbage at the end of the line after title,
                // but it could still be a valid reference if we roll back
                title = "";
                pos = destEndPos;
                lines = destEndLineNo;
                while (pos < max)
                {
                    ch = str.charAt(pos);
                    if (!Utils.isSpace(ch)) { break; }
                    pos++;
                }
            }
        }

        if (pos < max && str.charAt(pos) != 0x0A)
        {
            // garbage at the end of the line
            return false;
        }

        label = Utils.normalizeReference(StringUtil.slice(str, 1, labelEnd));
        if (StringUtil.isEmpty(label))
        {
            // CommonMark 0.20 disallows empty labels
            return false;
        }

        // Reference can not terminate anything. This check is for safety only.
        /*istanbul ignore if*/
        if (silent) { return true; }

        if (null == state.getEnv().references)
           state.getEnv().references = new HashMap<>();

        if (null == state.getEnv().references.get(label))
            state.getEnv().references.put(label, new Reference(href, title));

        state.setParentType(oldParentType);

        state.setLine(startLine + lines + 1);
        return true;
    }
}
